package com.haiqiu.tools.word.utils;


import com.haiqiu.tools.common.utils.HttpServletUtils;
import fr.opensagres.poi.xwpf.converter.pdf.PdfConverter;
import fr.opensagres.poi.xwpf.converter.pdf.PdfOptions;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBorder;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcBorders;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * wordUtils操作类
 */
public class WordUtils {

    public static final String DOCX = ".docx";
    public static final String PDF = ".pdf";

    /**
     * 临时文件名称
     */
    public static String tempFileName = String.valueOf(System.currentTimeMillis());

    private WordUtils() {
    }


    /**
     * @param input  word模板输入流
     * @param outPut 输出流
     * @param object 数据对象
     * @throws IllegalAccessException
     */
    public static void export(InputStream input, OutputStream outPut, Object object) throws IllegalAccessException {
        Map<String, Object> parameters = new HashMap<>();
        getParameters(object, object.getClass(), parameters);
        export(input, outPut, parameters);
    }

    /**
     * @param input      word模板输入流
     * @param outPut     输出流
     * @param parameters 填充数据Map
     */
    public static void export(InputStream input, OutputStream outPut, Map<String, Object> parameters) {
        try (XWPFDocument document = new XWPFDocument(input)) {
            // 填充text或者图片或者其他
            fillWord(document.getParagraphs(), parameters);
            // 填充表格
            fillTable(document, parameters);
            document.write(outPut);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 把对象解析成Map,如果是持有其他对象也会解析到map.
     * 1,如果去持有的对象有相同名称的filed,会被后面的覆盖.
     * 2,如果是持有其他对象的集合,现在只处理了数组,List,Map类型的没有进行处理
     *
     * @param object     数据对象
     * @param clazz      数据对象的class
     * @param parameters 解析的结果
     * @throws IllegalAccessException
     */
    private static void getParameters(Object object, Class<?> clazz, Map<String, Object> parameters) throws IllegalAccessException {
        Field[] fields = clazz.getDeclaredFields();
        Object value;
        for (Field field : fields) {
            field.setAccessible(true);
            value = getValue(field, object);
            if (field.getType().isArray()) {
                parsingCollection(field, object, parameters);
            } else if (isCustomObject(field.getType())) {
                parameters.put("${" + field.getName() + "}", value);
            } else {
                getParameters(value, field.getType(), parameters);
            }
        }
    }

    private static boolean isCustomObject(Class<?> clazz) {
        return clazz.isPrimitive() || clazz.getName().startsWith("java");
    }

    private static Object getValue(Field field, Object object) throws IllegalAccessException {
        return object == null || object == "" ? "" : field.get(object);
    }

    private static void parsingCollection(Field field, Object object, Map<String, Object> parameters) throws IllegalAccessException {
        Object value = getValue(field, object);
        List<Object> result = new ArrayList<>();
        if (value != null && value != "") {
            for (Object ob : (Object[]) value) {
                if (isCustomObject(ob.getClass())) {
                    result.add(ob);
                } else {
                    Map<String, Object> map = new HashMap<>();
                    Field[] fs = ob.getClass().getDeclaredFields();
                    for (Field f : fs) {
                        f.setAccessible(true);
                        if (f.getType().isArray()) {
                            parsingCollection(f, ob, map);
                        } else {
                            map.put("${" + f.getName() + "}", getValue(f, ob));
                        }
                    }
                    result.add(map);
                }
            }
        }
        parameters.put("${" + field.getName() + "}", result);
    }


    private static void fillWord(List<XWPFParagraph> paragraphs, Map<String, Object> parameters) {
        List<XWPFRun> runs;
        StringBuilder builder;
        Object value;
        for (XWPFParagraph paragraph : paragraphs) {
            runs = paragraph.getRuns();
            boolean start = false;
            builder = new StringBuilder();
            for (XWPFRun run : runs) {
                String text = run.text();
                if (text.startsWith("$")) {
                    start = true;
                }
                if (start) {
                    // 把标记去掉
                    run.setText("", 0);
                    builder.append(text);
                }
                if (text.endsWith("}")) {
                    start = false;
                    String key = builder.toString();
                    builder = new StringBuilder();
                    value = parameters.get(key);
                    if (value != null) {
                        // FIXME 自定义图片标记
                        if (key.equals("${图片标记}")) {
                            fillPicture(run, value.toString());
                        } else if (value instanceof List) {
                            // FIXME 数据通过逗号连接,或者分段
                            run.setText(value.toString());
                        } else if (key.equals("特殊处理")) {
                            // TODO 根据业务需求处理
                        } else {
                            run.setText(parameters.get(key).toString());
                        }
                    }
                }
            }
        }
    }

    /**
     * 填充图片.
     *
     * @param run  段落标记
     * @param path 文件路径
     */
    
    private static void fillPicture(XWPFRun run, String path) {
        // FIXME 图片输入流从网络读取
        BufferedImage img = getImgByUrl(path);
        if (img == null) {
            return;
        }

        int width = img.getWidth();
        int height = img.getHeight();
        //字节转输入流
        InputStream input = getImageStream(img);

        try {
            run.addPicture(input, Document.PICTURE_TYPE_PNG, tempFileName, Units.toEMU(width), Units.toEMU(height));
            input.close();
        } catch (InvalidFormatException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    
    public static BufferedImage getImgByUrl(String imgPath) {
        try {
            // FIXME 图片输入流从网络读取
            BufferedImage src = null;
            if (imgPath.startsWith("http")) {
                //定义一个URL对象
                URL url = new URL(imgPath);
                //打开连接
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                //设置请求方式为"GET"
                conn.setRequestMethod("GET");
                //超时响应时间为10秒
                conn.setConnectTimeout(10 * 1000);
                //通过输入流获取图片数据
                InputStream inStream = conn.getInputStream();
                //返回图片
                src = ImageIO.read(inStream);
            } else {
                File file = new File(imgPath);
                if (!file.exists()) {
                    System.err.println("" + imgPath + "   该文件不存在！");
                    return null;
                }
                src = ImageIO.read(new File(imgPath));
            }
            return src;
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }


    public static InputStream getImageStream(BufferedImage bimage){
        InputStream is = null;
        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        ImageOutputStream imOut;
        try {
            imOut = ImageIO.createImageOutputStream(bs);
            ImageIO.write(bimage, "png",imOut);
            is= new ByteArrayInputStream(bs.toByteArray());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return is;
    }

    /**
     * 填充表格
     *
     * @param document
     * @param parameters
     */
    @SuppressWarnings("all")
    private static void fillTable(XWPFDocument document, Map<String, Object> parameters) {
        List<XWPFTable> tables = document.getTables();
        Object values;
        for (XWPFTable table : tables) {
            if (isFillTable(table)) {
                // 标记行只有一格
                values = parameters.get(table.getRow(1).getTableCells().get(1).getText());
                List<String> keys = getTableKeys(table);
                if (values != null && values != "") {
                    List<Object> obs = (List<Object>) values;
                    int cellSize = table.getRow(0).getTableCells().size();
                    XWPFTableRow row;
                    List<XWPFTableCell> cells;
                    String str;
                    for (Object ob : obs) {
                        row = table.createRow();
                        cells = row.getTableCells();
                        // 如果是list,表示只有一列,直接进行填充
                        if (ob instanceof List) {
                            List<String> strs = (List<String>) ob;
                            for (int i = 0; i < cellSize; i++) {
                                str = i < strs.size() ? strs.get(i) : "";
                                cells.get(i).setText(str);
                            }
                        }
                        // 如果是map,表示有多列,根据表进行填充
                        if (ob instanceof Map) {
                            Map<String, Object> maps = (Map<String, Object>) ob;
                            for (int i = 0; i < cellSize; i++) {
                                str = i < keys.size() ?
                                        maps.get(keys.get(i)) == null ? "" : maps.get(keys.get(i)).toString() : "";
                                cells.get(i).setText(str);
                            }
                        }
                        if (ob instanceof String) {
                            for (int i = 0; i < cellSize; i++) {
                                cells.get(i).setText(ob == null ? "" : ((String) ob).toLowerCase());
                            }
                        }
                    }
                }
                setTableCellBorders(table);
            } else {
                //替换表格中的标记数据
                table.getRows().forEach(row -> {
                    row.getTableCells().forEach(cell -> fillWord(cell.getParagraphs(), parameters));
                });
            }
        }
    }


    /**
     * 判断是否需要增加表格.
     * 如果表格有多行,且第一行是需要填充的标记行,则需要增加填充表格
     *
     * @param table table
     * @return
     */
    private static boolean isFillTable(XWPFTable table) {
        if (table.getRows().size() < 2) {
            return false;
        }
        List<XWPFTableCell> cells = table.getRows().get(1).getTableCells();
        return cells.size() == 1 && cells.get(0).getText().startsWith("$");
    }

    private static List<String> getTableKeys(XWPFTable table) {
        List<String> keys = new ArrayList<>();
        List<XWPFTableRow> rows = table.getRows();
        XWPFTableRow row = rows.size() > 2 ? rows.get(2) : rows.get(1);
        List<XWPFTableCell> cells = row.getTableCells();
        for (XWPFTableCell cell : cells) {
            keys.add(cell.getText());
        }
        if (table.getRows().size() > 2) {
            table.removeRow(2);
        }
        table.removeRow(1);
        return keys;
    }

    /**
     * 设置单元格格式.
     *
     * @param table 表格
     */
    private static void setTableCellBorders(XWPFTable table) {
        table.getRows().forEach(row -> {
            row.getTableCells().forEach(WordUtils::setBorders);
        });
    }

    private static void setBorders(XWPFTableCell cell) {
        cell.setWidthType(TableWidthType.AUTO);
        CTTcPr tcPr = cell.getCTTc().getTcPr();
        if (tcPr == null) {
            tcPr = cell.getCTTc().addNewTcPr();
        }
        CTTcBorders borders = tcPr.getTcBorders();
        if (borders == null) {
            borders = tcPr.addNewTcBorders();
        }
        setBorder(borders.addNewTop());
        setBorder(borders.addNewBottom());
        setBorder(borders.addNewLeft());
        setBorder(borders.addNewRight());

    }

    private static void setBorder(CTBorder boder) {
        boder.setVal(STBorder.SINGLE);
        boder.setSz(BigInteger.valueOf(2));
        boder.setColor("000000");
    }


    /**
     * 新增的自定义导出
     *
     * @param wordPath word模板地址。可以是网络地址
     * @param object   数据对象
     * @param fileName 导出文件名称
     */
    
    public static void exportDocx(String wordPath, Object object, String fileName) {
        Map<String, Object> parameters = new HashMap<>();
        try {
            getParameters(object, object.getClass(), parameters);
            export(Files.newInputStream(Paths.get(wordPath)), parameters, fileName);
        } catch (IllegalAccessException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    
    public static void exportDocx(String wordPath, Map<String, Object> parameters, String fileName) {
        try {
            export(Files.newInputStream(Paths.get(wordPath)), parameters, fileName);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    
    public static void exportDocx(InputStream in, Map<String, Object> parameters, String fileName) {
        export(in, parameters, fileName);
    }

    
    public static void exportPdf(String wordPath, Map<String, Object> parameters, String fileName) {
        try {
            export(Files.newInputStream(Paths.get(wordPath)), parameters, fileName, PDF);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    
    public static void exportPdf(InputStream in, Map<String, Object> parameters, String fileName) {
        export(in, parameters, fileName, PDF);
    }

    public static void export(InputStream input, Map<String, Object> parameters, String fileName) {
        try (XWPFDocument document = new XWPFDocument(input)) {
            // 填充text或者图片或者其他
            fillWord(document.getParagraphs(), parameters);
            // 填充表格
            fillTable(document, parameters);
            export(fileName, document, HttpServletUtils.getServletResponse());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void export(InputStream input, Map<String, Object> parameters, String fileName, String type) {
        try (XWPFDocument document = new XWPFDocument(input)) {
            // 填充text或者图片或者其他
            fillWord(document.getParagraphs(), parameters);
            // 填充表格
            fillTable(document, parameters);
            export(fileName, document, HttpServletUtils.getServletResponse(), type);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 导出文件流
     *
     * @param fileName 文件名
     * @param document 工作铺
     * @param response 输出流
     */
    private static void export(String fileName, XWPFDocument document, HttpServletResponse response) {
        export(fileName, document, response, DOCX);
    }

    /**
     * 导出文件流
     *
     * @param fileName 文件名
     * @param document 工作铺
     * @param response 输出流
     */
    private static void export(String fileName, XWPFDocument document, HttpServletResponse response, String type) {
        try (OutputStream outputStream = response.getOutputStream()) {
            // 设置输出的格式
            String name = URLEncoder.encode(fileName + type, "UTF-8");
            //自定义的header
            response.setHeader("requestType", "file");
            // 自定义的header
            response.setHeader("requestFileName", name);
            //设置这个header 可见
            response.setHeader("Access-Control-Expose-Headers", "requestType,requestFileName");

            //设置响应类型为html，编码为utf-8，处理相应页面文本显示的乱码
            response.setContentType("application/octet-stream;charset=UTF-8");
            //设置文件头：最后一个参数是设置下载文件名
            response.setHeader("Content-disposition", "attachment;fileName=" + name);
            if (type.equals(DOCX)) {
                document.write(outputStream);
                outputStream.flush();
            } else if (type.equals(PDF)) {
                docCovertPdf(document, fileName);
                downloadLocal(document, fileName, response);
            }
            //删除临时文件
            new File(tempFileName).delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void docCovertPdf(XWPFDocument doc, String pdfName) {
        PdfOptions options = PdfOptions.create();

        try (OutputStream outPDF = Files.newOutputStream(Paths.get(pdfName))) {
            PdfConverter.getInstance().convert(doc, outPDF, options);
            outPDF.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void downloadLocal(XWPFDocument doc, String pdfName, HttpServletResponse response) {
        // 下载本地文件
        String fileName = pdfName; // 文件的默认保存名
        // 读到流中
        InputStream inStream = null;// 文件的存放路径
        try {
            inStream = Files.newInputStream(Paths.get(fileName));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 循环取出流中的数据
        byte[] b = new byte[1024];
        int len;
        try {
            while ((len = inStream.read(b)) > 0) {
                response.getOutputStream().write(b, 0, len);
            }
            inStream.close();
            //删除本地生成的临时文件
            new File(fileName).delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


